﻿using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;

namespace zijian666.Core.Abstractions.Reflection
{
    class ExpressionReflectCompiler : IFeature, IReflectCompiler
    {
        public ObjectCreator<Object> CompileCreator<Object>(ConstructorInfo constructor)
        {
            throw new NotImplementedException();
        }

        public MemberGetter<Value> CompileGetter<Value>(PropertyInfo property)
        {
            if (property is null)
            {
                throw new ArgumentNullException(nameof(property));
            }

            if (property.ReflectedType == null)
            {
                throw new ArgumentException(nameof(property.ReflectedType) + " is null");
            }

            var method = property.GetGetMethod(true);
            if (method == null)
            {
                //if (property.ReflectedType.GetField($"<{property.Name}>k__BackingField", (BindingFlags)(-1)) is FieldInfo field)
                //{
                //    return BuildFieldGetter<T>(field);
                //}
                return null;
            }

            var param1 = Expression.Parameter(typeof(object), "param1");            // func(object param1)

            var instance = method.IsStatic ? null : Expression.Convert(param1, property.ReflectedType);      // var instance = (type)param1

            var prop = Expression.Property(instance, property);                     // var prop = instance.property
            var @return = Expression.Convert(prop, typeof(Value));                      // return (T)prop
            return Expression.Lambda<MemberGetter<Value>>(@return, param1).Compile();
        }

        public MemberSetter<Value> CompileSetter<Value>(PropertyInfo property)
        {
            if (property is null)
            {
                throw new ArgumentNullException(nameof(property));
            }

            if (property.ReflectedType == null)
            {
                throw new ArgumentException(nameof(property.ReflectedType) + " is null");
            }

            var method = property.GetSetMethod(true);
            if (method == null)
            {
                //if (property.ReflectedType.GetField($"<{property.Name}>k__BackingField", (BindingFlags)(-1)) is FieldInfo field)
                //{
                //    return BuildFieldSetter<T>(field);
                //}
                return null;
            }

            var param1 = Expression.Parameter(typeof(object), "param1");
            var param2 = Expression.Parameter(typeof(Value), "param2");                 // action(object param1,T param2)

            var instance = method.IsStatic ? null : Expression.Convert(param1, property.ReflectedType);      // var instance = (type)param1
            var value = Expression.Convert(param2, property.PropertyType);          // var value = (propertyType)param2

            var assign = Expression.MakeBinary(ExpressionType.Assign,
                                    Expression.Property(instance, property), value);// instance.property = value
            //var ret = Expression.Convert(assign, typeof(object));               
            return Expression.Lambda<MemberSetter<Value>>(assign, param1, param2).Compile();
        }

        public MemberGetter<Value> CompileGetter<Value>(FieldInfo field)
        {
            if (field is null)
            {
                throw new ArgumentNullException(nameof(field));
            }

            if (field.ReflectedType == null)
            {
                throw new ArgumentException(nameof(field.ReflectedType) + " is null");
            }

            var param1 = Expression.Parameter(typeof(object), "param1");            // func(object param1)
            var instance = field.IsStatic ? null : Expression.Convert(param1, field.ReflectedType);         // var instance = (type)param1

            var fld = Expression.Field(instance, field);                            // var fld = instance.field
            var @return = Expression.Convert(fld, typeof(Value));                       // return (T)fld
            return Expression.Lambda<MemberGetter<Value>>(@return, param1).Compile();
        }

        public MemberSetter<Value> CompileSetter<Value>(FieldInfo field)
        {
            if (field is null)
            {
                throw new ArgumentNullException(nameof(field));
            }

            if (field.ReflectedType == null)
            {
                throw new ArgumentException(nameof(field.ReflectedType) + " is null");
            }

            if (field.IsLiteral)
            {
                throw new ArgumentException(nameof(field) + " is literal");
            }

            if (field.IsInitOnly)
            {
                throw new ArgumentException(nameof(field) + " is readonly");
            }

            var param1 = Expression.Parameter(typeof(object), "param1");
            var param2 = Expression.Parameter(typeof(Value), "param2");             // action(object param1,T param2)

            var instance = field.IsStatic ? null : Expression.Convert(param1, field.ReflectedType);     // var instance = (type)param1
            var value = Expression.Convert(param2, field.FieldType);            // var value = (fieldType)param2

            var assign = Expression.MakeBinary(ExpressionType.Assign, Expression.Field(instance, field), value);  // instance.field = value

            return Expression.Lambda<MemberSetter<Value>>(assign, param1, param2).Compile();
        }

        public MethodCaller<Result> CompileCaller<Result>(MethodInfo method)
        {
            throw new NotImplementedException();
        }
    }
}
